#include <Ape/IO/FS/RegularFile.h>

#include <Ape/EInvalidArgument.h>
#include <Ape/EIndexOutOfRange.h>
#include <Ape/EInvalidOperation.h>
#include <Ape/ENotImplemented.h>
#include <Ape/Collections/Array.h>
#include <Ape/IO/Console.h>
#include <Ape/Int.h>

#include <stdio.h>
#include <unistd.h>

namespace Ape {
	namespace IO {
		namespace FS {
			RegularFile::RegularFile (const Path& path)
					: FSObject (path)
					, m_Mode (oNone)
					, m_Fp (NULL)
			{}
			RegularFile::RegularFile ()
					: FSObject (L"")
					, m_Mode (oNone)
					, m_Fp (NULL)
			{}
			
			RegularFile::~RegularFile() {
				Close();
			}
			
			void RegularFile::SetPath(const Path& path) {
				Close();
				m_Path = path;
			}
			
			const char* RegularFile::StringForAccessType (AccessType access) {
				switch (access) {
					case oReadBinary :
						return "rb";
					case oRead :
						return "r";
					case oWriteBinary :
						return "wb";
					case oWrite :
						return "w";
					case oAppendBinary :
						return "ab";
					case oAppend :
						return "a";
					case oNone :
						return "--";
					default:
						throw Ape::EInvalidArgument (L"Unknown access type for file open");
				}
			}
			
			
			bool RegularFile::CanIReadTypedData (AccessType access) {
				switch (access) {
					case oReadBinary:
						return true;
					default:
						return false;
				}
			}
			bool RegularFile::CanIWriteTypedData (AccessType access) {
				switch (access) {
					case oWriteBinary:
					case oAppendBinary:
						return true;
					default:
						return false;
				}
			}
			
			bool RegularFile::CanIRead (AccessType access) {
				switch (access) {
					case oRead:
					case oReadBinary:
						return true;
					default:
						return false;
				}
			}
			bool RegularFile::CanIWrite (AccessType access) {
				switch (access) {
					case oWrite:
					case oWriteBinary:
					case oAppend:
					case oAppendBinary:
						return true;
					default:
						return false;
				}
			}
			
			bool RegularFile::ShouldICheckFileExistance (AccessType access) {
				switch (access) {
					case oRead:
					case oReadBinary:
						return true;
					case oNone:
					case oAppend:
					case oAppendBinary:
					case oWrite:
					case oWriteBinary:
						return false;
					default:
						throw Ape::EInvalidArgument (L"Unknown access type for file open");
				}
			}
			
			void RegularFile::Open (AccessType access) {
				// TODO: check whether the class already has an open file handle
				Close();
				if (ShouldICheckFileExistance (access) ) {
					get_Stat();
				}
				char* csFName = NULL;
				m_Path.ToString().ToLatin1 (&csFName);
				m_Fp = fopen (csFName, StringForAccessType (access) );
				if (!m_Fp)
					throw Exception (L"Failed to open file");
				m_Mode = access;
				del_a (csFName);
				//m_CurrentPosition = ftell (m_Fp);
				
			}
			void RegularFile::Close() {
				if (m_Fp) {
					fclose (m_Fp);
					m_Fp = NULL;
					m_Mode = oNone;
				}
			}
			void RegularFile::Flush() {
				if (m_Fp) {
					fflush (m_Fp);
				}
			}
			
			__attribute_warn_unused_result__
			size_t RegularFile::Read (Buffer_t& buff, size_t useBuffSize) {
				if (!CanIRead (m_Mode) ) {
					throw Ape::EInvalidOperation (L"File is not open or is opened in an incorrect mode");
				}
				size_t iBuffSize = buff.get_Size();
				if (useBuffSize > 0 && useBuffSize < iBuffSize) {
					iBuffSize = useBuffSize;
				}
				unsigned char* realBuff = new unsigned char[iBuffSize];
				
				int iOldPos = /*m_CurrentPosition =*/ Tell ();
				size_t actuallyRead = fread (realBuff, iBuffSize, 1, m_Fp);
				int iNewPos = /*m_CurrentPosition =*/ Tell ();
				actuallyRead = iNewPos - iOldPos;
				
				for (size_t i = 0; i < iBuffSize; i++) {
					if (i < actuallyRead) {
						buff[i] = realBuff[i];
					}
					else {
						buff[i] = NULL;
					}
				}
				del_a (realBuff);
				return actuallyRead;
			}
			
			__attribute_warn_unused_result__
			size_t RegularFile::Write (const Buffer_t& buff, size_t useBuffSize) {
				if (!CanIWrite (m_Mode) ) {
					throw Ape::EInvalidOperation (L"File is not open or is opened in an incorrect mode");
				}
				size_t iBuffSize = buff.get_Size();
				if (useBuffSize > 0 && useBuffSize < iBuffSize) {
					iBuffSize = useBuffSize;
				}
				
				unsigned char* realBuff = new unsigned char[iBuffSize];
				
				for (size_t i = 0; i < iBuffSize; i++) {
					realBuff[i] = buff[i];
				}
				
				int iOldPos = /*m_CurrentPosition =*/ Tell ();
				size_t actuallyWritten = fwrite (realBuff, iBuffSize, 1, m_Fp);
				int iNewPos = /*m_CurrentPosition =*/ Tell ();
				actuallyWritten = iNewPos - iOldPos;
				
				del_a (realBuff);
				return actuallyWritten;
			}
			
			void RegularFile::Unlink (bool quiet) {
				if (!quiet) {
					get_Stat();
				}
				char* csFName = NULL;
				m_Path.ToString().ToLatin1 (&csFName);
				unlink (csFName);
				del_a (csFName);
			}
			
			void RegularFile::Seek (size_t iOffset, SeekOrigin origin) {
				if (!m_Fp) {
					throw Ape::EInvalidOperation (L"File is not open");
				}
				int errCode = fseek (m_Fp, iOffset, origin);
				if (errCode) {
					throw Ape::EIndexOutOfRange (L"You're most likely trying to put cursor outside of file's bounds");
				}
				//m_CurrentPosition = ftell(m_Fp);
			}
			
			size_t RegularFile::Tell() const {
				if (!m_Fp) {
					throw Ape::EInvalidOperation (L"File is not open");
				}
				return ftell (m_Fp);
			}
			
			void RegularFile::_Dump (bool verbose) const {
				FSObject::_Dump (verbose);
				
				DUMP_NON_V_TYPE;
				
				using Ape::IO::StdOut;
				using Ape::IO::NewLine;
				int iPtr = int (m_Fp);
				StdOut << L"\t\tHandle:\t" << Ape::Int (iPtr).ToString (radixHex) << NewLine
				<< L"\t\tMode:\t" << String (StringForAccessType (m_Mode) ) << NewLine
				<< L"\t\tCursor:\t"
				<< (m_Fp ? Ape::Int (Tell() ).ToString() : Ape::Int (-1).ToString() )
				<< NewLine;
			}
			
			void RegularFile::WriteString (const String& str) {
				//throw Ape::ENotImplemented (L"Encodings support is not currently implemented");
				Buffer_t buff(str.get_Length(), L'\0');
				for (size_t i = 0; i < str.get_Length(); i++) {
					buff[i] = str.WcharAt(i);
				}
				Write(buff);
			}
			String RegularFile::ReadString (int maxLen) {
				throw Ape::ENotImplemented (L"Encodings support is not currently implemented");
			}
			
			AccessType RegularFile::get_Mode() const {
				return m_Mode;
			}
			
			size_t RegularFile::SubmergeBytes(const Ape::Collections::Array<unsigned char>& bytes) {
				if (!CanIWriteTypedData(get_Mode())) // we need binary access here
					throw Ape::EInvalidOperation(L"Cannot submerge: the sink is opened for emerging");
				return Write(bytes);
			}
			void RegularFile::EmergeBytes(Ape::Collections::Array<unsigned char>& bytes) {
				if (!CanIReadTypedData(get_Mode())) // we need binary access here
					throw Ape::EInvalidOperation(L"Cannot emerge: the sink is opened for submerging");
				size_t actuallyRead = Read(bytes);
				
				if (actuallyRead != bytes.get_Size()) {
					// Actually we should throw an Ape::Serialization::SerializationException
					throw Ape::EInvalidFormat(L"Could not emerge enough data. Do you read from a wrong sink?");
				}
			}
			
		}
	}
}
